package ui;

import java.applet.Applet;
import java.applet.AudioClip;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.LinkedList;
import java.util.Random;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import javax.swing.Action;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultComboBoxModel;
import javax.swing.GrayFilter;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JToolBar;
import javax.swing.ScrollPaneConstants;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.event.HyperlinkEvent.EventType;

import board.Board;
import board.NodeMark;

import sgf.SGFController;
import tree.ITreeListener;
import tree.PageNode;
import tree.PageTree;
import ui.actions.ClearSGFAction;
import ui.actions.EditSGFAction;
import ui.actions.ImportSGFAction;
import ui.actions.InsertNodeAction;
import ui.actions.InsertNodeCopyAction;
import ui.actions.LinkAction;
import ui.actions.NewAction;
import ui.actions.NewSGFAction;
import ui.actions.NextHistoryAction;
import ui.actions.NextPageAction;
import ui.actions.PreferencesAction;
import ui.actions.PreviousHistoryAction;
import ui.actions.PreviousPageAction;
import ui.actions.SetBoardSizeAction;
import ui.actions.StoneAction;
import ui.actions.OpenAction;
import ui.actions.SaveAction;
import ui.actions.SaveAsAction;
import ui.tools.EditTool;
import ui.tools.LabelTool;
import ui.tools.LetterTool;
import ui.tools.MarkTool;
import ui.tools.NumberTool;
import ui.tools.StoneTool;
import ui.tools.TerritoryTool;
import util.GS;
import util.SimpleFileFilter;
import util.Util;

/**
 * GoViewer main frame.
 * @author TKington
 *
 */
public class Main extends JFrame
				implements ITreeListener, HyperlinkListener {
	/** RNG */
	public static Random rand = new Random();
	
	//	TODO: look at colors
	/** Background color for the window. */
	public static final Color BGCOLOR = new Color(228, 234, 219);
	
	/** File filter for gbk files. */
	public static SimpleFileFilter gbkFilter = 
		new SimpleFileFilter("Book files", "gbk");
	
	/** The one and only Main instance. */
    public static Main frame;

    /** Add stone tool. */
	private EditTool TOOL_STONE;
	/** Triangle mark tool. */
	private EditTool TOOL_TRI;
	/** Circle mark tool. */
	private EditTool TOOL_CIR;
	/** Square mark tool. */
	private EditTool TOOL_SQU;
	/** X mark tool. */
	private EditTool TOOL_X;
	/** Letter tool. */
	private EditTool TOOL_LETTER;
	/** Number tool. */
	private EditTool TOOL_NUMBER;
	/** Label tool. */
	private EditTool TOOL_LABEL;
	/** Territory tool. */
	private EditTool TOOL_TERRITORY;
	
	/** The text pane. */
    private TextEditorPane textPane;
    /** The page tree. */
    private PageTree tree;
    /** The splitter. */
    private JSplitPane splitPane;
    /** The center panel. */
    private JPanel centerPanel;
    /** The GobanPanel. */
    private GobanPanel goban;
    /** The WidgetPanel. */
    private WidgetPanel widgetPanel;
    /** The EditController. */
    private EditController editController;
    
    /** Sound of a stone being played. */
    public static AudioClip clickSound;
    
    /** New file action. */
    private Action newAction;
    /** Open file action. */
    private Action openAction;
    /** Save file action. */
    private Action saveAction;
    /** Save as action. */
    private Action saveAsAction;
    
    /** Previous page action. */
    private Action prevAction;
    /** Next page action. */
    private Action nextAction;
    /** Previous in history action. */
    private Action prevHistAction;
    /** Next in history action. */
    private Action nextHistAction;
    
    /** Select stone tool action. */
    private Action stoneAction;
    
    /** Insert blank node action. */
    private Action insertNodeAction;
    /** Insert page copy action. */
    private Action insertNodeCopyAction;
    /** Import SGF action. */
    private Action importSGFAction;
    /** New SGF action. */
    private Action newSGFAction;
    /** Edit SGF action. */
    private Action editSGFAction;
    /** Clear SGF action. */
    private Action clearSGFAction;
    /** Insert hyperlink action. */
    private Action linkAction;
    /** Set board size action. */
    private Action setSizeAction;
    /** Open preferences action. */
    private Action prefsAction;
    
    /** Combo of edit tools. */
    private JComboBox toolCombo;	
	/** ComboBoxModel for the toolCombo. */
	private DefaultComboBoxModel toolComboModel;
	/** Gray triangle image for use when toolCombo is disabled. */
	private ImageIcon grayTri;
    /** Edit/view HTML combo. */
    private JComboBox textModeCombo;
    
    /** Name of current file. */
    private String filename;
    /** Current File. */
    private File curFile;
    /** Has current file changed since saving? */
    private boolean dirty;
    /** Current page. */
    private PageNode curPage;
    /** Allowed to edit file? */
    public final boolean editMode;
    
    /** WidgetPanel displayed? */
    private boolean widgetPanelVisible = false;
    
    /** Stack of pages before current page in history. */
    private LinkedList<Integer> historyStack = new LinkedList<Integer>();
    /** Stack of pages after current page in history. */
    private LinkedList<Integer> futureStack = new LinkedList<Integer>();
    
    /**
     * Creates the main frame.
     * @param editMode true to allow editing files
     */
    public Main(boolean editMode) {
    	super("GoViewer");
    
    	frame = this;
    	this.editMode = editMode;
    	
    	try {
    		GS.loadSettings();
    	}
    	catch(FileNotFoundException e) {
    		JOptionPane.showMessageDialog(this, "viewer.dat not found.  A new file will be created.");
    	}
    	catch(Exception e) {
    		Util.log(e);
    	}
    	
    	if(GS.getClickSoundEnabled())
    		loadSounds();
    	
    	
		TOOL_STONE = new StoneTool();
		TOOL_TRI = new MarkTool(NodeMark.TRI);
		TOOL_CIR = new MarkTool(NodeMark.CIR);
		TOOL_SQU = new MarkTool(NodeMark.SQU);
		TOOL_X = new MarkTool(NodeMark.X);
		TOOL_LETTER = new LetterTool();
		TOOL_NUMBER = new NumberTool();
		TOOL_LABEL = new LabelTool();
		TOOL_TERRITORY = new TerritoryTool();
    	
    	goban = new GobanPanel(new Board(19));
    	goban.turnOffGhost();
    	textPane = TextEditorPane.instance();
    	textPane.addHyperlinkListener(this);
        
    	editController = EditController.instance();
    	editController.setEditTool(TOOL_STONE);
    	editController.setGoban(goban);
    	
    	SGFController sgfController = SGFController.instance();
    	sgfController.setPanel(goban);
    	sgfController.setCommentArea(textPane);
    	widgetPanel = new WidgetPanel(sgfController);
    	sgfController.setWidgetPanel(widgetPanel);
    	
    	tree = PageTree.instance();
    	
    	createActions();
    	Box toolbarPanel = new Box(BoxLayout.X_AXIS);
    	toolbarPanel.add(createToolBar());
    	toolbarPanel.add(Box.createHorizontalGlue());
    	
    	add(toolbarPanel, BorderLayout.NORTH);
    	
    	tree.addTreeListener(this);

    	JScrollPane treeScrollPane = new JScrollPane();
        treeScrollPane.setViewportView(tree);
        treeScrollPane.setPreferredSize(new Dimension(200, 200));
    	add(treeScrollPane, BorderLayout.WEST);
    	
    	centerPanel = new JPanel(new BorderLayout());
    	centerPanel.add(goban);
    	
    	//	Calculate size for widgetPanel, and set min for goban so that 
    	//	splitPane divider can't be moved too far to the left.
    	widgetPanel.setToPlay(1);
    	widgetPanel.clearSolved();
    	Dimension dim = goban.getMinimumSize();
    	Dimension widgetDim  = widgetPanel.getLayout().minimumLayoutSize(widgetPanel);
    	dim.width = Math.max(dim.width, widgetDim.width);
    	goban.setMinimumSize(dim);
    	goban.setPreferredSize(dim);
    	
    	textPane.setEditable(false);
        textPane.addKeyListener(new KeyListener() {
        	public void keyTyped(java.awt.event.KeyEvent e) {
        		setDirty(true);
        	}
        	
        	public void keyPressed(KeyEvent e) { /* */ }
        	public void keyReleased(KeyEvent e) { /* */ }
        });
        
        JScrollPane scrollPane = new JScrollPane(textPane);
        scrollPane.setHorizontalScrollBarPolicy(
        		ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPane.setVerticalScrollBarPolicy(
        		ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
    	
        splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, 
        									centerPanel, 
        									scrollPane);
        add(splitPane);
    	
    	addWindowListener(new WindowAdapter() {
    		@Override
			public void windowClosing(WindowEvent e) {
    			onExit();
    		}
    	});
    	
    	setBounds(GS.getFrameBounds());
    	splitPane.setDividerLocation(GS.getSplitterPos());
    	
    	curFile = GS.getLastFile();
    	if(curFile != null && curFile.exists()) {
    		onOpenFile(curFile);
    		
    		historyStack = GS.getHistoryStack();
    		futureStack = GS.getFutureStack();
    		
    		while(true) {
    			PageNode lastPage = tree.getPage(historyStack.getLast());
    			
    			if(lastPage != null) {
    				gotoPage(lastPage, false);
    				break;
    			}
    			
    			historyStack.removeLast();
    		}
    	}
    	else onNewFile();

    	setVisible(true);
    }

    /** Creates UI actions for toolbar. */
    private void createActions() {
    	newAction = new NewAction();
    	openAction = new OpenAction();
    	saveAction = new SaveAction();
    	saveAsAction = new SaveAsAction();
    	
    	prevAction = new PreviousPageAction();
    	nextAction = new NextPageAction();
    	prevHistAction = new PreviousHistoryAction();
    	nextHistAction = new NextHistoryAction();

    	stoneAction = new StoneAction();

    	insertNodeAction = new InsertNodeAction();
    	insertNodeCopyAction = new InsertNodeCopyAction();
    	newSGFAction = new NewSGFAction();
    	importSGFAction = new ImportSGFAction();
    	editSGFAction = new EditSGFAction();
    	clearSGFAction = new ClearSGFAction();
    	linkAction = new LinkAction();
    	setSizeAction = new SetBoardSizeAction();
    	prefsAction = new PreferencesAction();
    }
    
    /**
     * Creates the toolbar.
     * @return the new JToolBar
     */
    private JToolBar createToolBar() {
        JToolBar toolbar = new JToolBar();
        toolbar.setFloatable(false);
        
        JButton b;
        if(editMode) {
	        b = new JButton(newAction);
	        b.setText(""); //$NON-NLS-1$
	        toolbar.add(b);
        }
        
        b = new JButton(openAction);
        b.setText(""); //$NON-NLS-1$
        toolbar.add(b);
        
        if(editMode) {
	        b = new JButton(saveAction);
	        b.setText(""); //$NON-NLS-1$
	        toolbar.add(b);
	        
	        b = new JButton(saveAsAction);
	        b.setText(""); //$NON-NLS-1$
	        toolbar.add(b);
        }
        
        toolbar.addSeparator();

        b = new JButton(prevAction);
        b.setText("");
        toolbar.add(b);
        
        b = new JButton(nextAction);
        b.setText("");
        toolbar.add(b);
        
        toolbar.addSeparator();
        
        b = new JButton(prevHistAction);
        b.setText("");
        toolbar.add(b);
        
        b = new JButton(nextHistAction);
        b.setText("");
        toolbar.add(b);
        
        if(editMode) {
	        toolbar.addSeparator();
	        
	        b = new JButton(stoneAction); //$NON-NLS-1$
	        b.setText(""); //$NON-NLS-1$
	        toolbar.add(b);
	        
	        ImageIcon tri = Util.getIcon("TriStone.png");
	        grayTri = new ImageIcon(GrayFilter.createDisabledImage(tri.getImage()));
	        
	        Object [] items = new Object[] {
	        		tri, //$NON-NLS-1$
	        		Util.getIcon("CircleStone.png"), //$NON-NLS-1$
	        		Util.getIcon("SquareStone.png"), //$NON-NLS-1$
	        		Util.getIcon("XStone.png"), //$NON-NLS-1$
	        		Util.getIcon("AStone.png"), //$NON-NLS-1$
	        		Util.getIcon("OneStone.png"), //$NON-NLS-1$
					Util.getIcon("LabelStone.png"), //$NON-NLS-1$
					Util.getIcon("Territory.png")}; //$NON-NLS-1$
	        toolCombo = new JComboBox(items);
	        toolComboModel = (DefaultComboBoxModel)toolCombo.getModel();
	        Dimension dim = toolCombo.getMinimumSize();
	        toolCombo.setMaximumSize(new Dimension(dim.width, 100));
	        toolCombo.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					switch(toolCombo.getSelectedIndex()) {
					case 0: editController.setEditTool(TOOL_TRI); break;
					case 1: editController.setEditTool(TOOL_CIR); break;
					case 2: editController.setEditTool(TOOL_SQU); break;
					case 3: editController.setEditTool(TOOL_X); break;
					case 4: editController.setEditTool(TOOL_LETTER); break;
					case 5: editController.setEditTool(TOOL_NUMBER); break;
					case 6: editController.setEditTool(TOOL_LABEL); break;
					case 7: editController.setEditTool(TOOL_TERRITORY); break;
					}
				}
			});
	        toolbar.add(toolCombo);
	        
	        toolbar.addSeparator();
	        
	        String [] modeChoices = {"View Text", "Edit Text"};
	        textModeCombo = new JComboBox(modeChoices);
	        textModeCombo.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					int mode = -1;
					switch(textModeCombo.getSelectedIndex()) {
					case 0:
						mode = TextEditorPane.TEXTMODE_VIEWHTML;
						break;
					case 1: 
						mode = TextEditorPane.TEXTMODE_EDITHTML;
						setDirty(true);
						break;
					}
					curPage.setTextPaneMode(mode);
				}
			});
	        toolbar.add(textModeCombo);
        
	        toolbar.addSeparator();
	        
	        b = new JButton(insertNodeAction);
	        b.setText("");
	        toolbar.add(b);
	        
	        b = new JButton(insertNodeCopyAction);
	        b.setText("");
	        toolbar.add(b);
	        
	        toolbar.addSeparator();
	        
	        b = new JButton(newSGFAction);
	        toolbar.add(b);
	        
	        b = new JButton(importSGFAction);
	        b.setText("");
	        toolbar.add(b);
	        
	        b = new JButton(editSGFAction);
	        b.setText("");
	        toolbar.add(b);
	        
	        b = new JButton(clearSGFAction);
	        b.setText("");
	        toolbar.add(b);
	        
	        toolbar.addSeparator();
	        
	        b = new JButton(linkAction);
	        b.setText("");
	        toolbar.add(b);
	        
	        b = new JButton(setSizeAction);
	        b.setText("");
	        toolbar.add(b);
        }
        
        b = new JButton(prefsAction);
        b.setText("");
        toolbar.add(b);
        
        return toolbar;
    }
    
    /**
     * Sets the currently selection in the text mode combo.
     * @param mode one of the constants defined in TextEditorPane
     */
    public void setTextModeCombo(int mode) {
    	switch(mode) {
    	case TextEditorPane.TEXTMODE_VIEWHTML:
    		textModeCombo.setSelectedIndex(0);
    		return;
    	case TextEditorPane.TEXTMODE_EDITHTML:
    		textModeCombo.setSelectedIndex(1);
    		return;
    	}
    	throw new RuntimeException("" + mode);
    }
    
    public void treeSelectionChanged(PageNode newPage) {
		gotoPage(newPage, true);
	}
    
    public void treeChanged() {
		setDirty(true);
	}
    
    /** Shows the WidgetPanel. */
    public void showWidgetPanel() {
    	if(!widgetPanelVisible) {
			centerPanel.add(widgetPanel, BorderLayout.NORTH);
			centerPanel.validate();
			widgetPanelVisible = true;
    	}
    }
    
    /** Hides the WidgetPanel. */
    public void hideWidgetPanel() {
    	if(widgetPanelVisible) {
    		centerPanel.remove(widgetPanel);
    		centerPanel.validate();
    		widgetPanelVisible = false;
    	}
    }
    
    /**
     * Goes to a particular page.
     * @param dest the destination page
     * @param addToHistory true to add to the history
     */
	public void gotoPage(PageNode dest, boolean addToHistory) {
    	if(curPage != null)
    		curPage.leavingPage();
    	
    	curPage = dest;
    	curPage.display(goban);
    	
    	goban.repaint();
    	
    	tree.setSelectionWithoutEvents(dest);
    	
    	if(addToHistory) {
    		historyStack.addLast(dest.getID());
    		if(historyStack.size() > 500)
    			historyStack.removeFirst();
    		
    		futureStack.clear();
    	}
    }
    
    public void hyperlinkUpdate(HyperlinkEvent e) {
    	if(e.getEventType() != EventType.ACTIVATED)
    		return;
    	
		String url = e.getDescription();
		if(url.startsWith("node:")) {
			try {
				int id = Integer.parseInt(url.substring(5));
				PageNode nextPage = tree.getPage(id);
				if(nextPage == null) {
					JOptionPane.showMessageDialog(this,
							"Page does not exist");
				}
				else gotoPage(nextPage, true);
			}
			catch(NumberFormatException ex) {
				JOptionPane.showMessageDialog(this, "Invalid link format");
			}
		}
	}
    
    /**
     * Enables/disables UI actions based on attributes of the current page.
     * @param editable true if the current page is editable
     * @param containsSGF true if the current page contains an SGF problem
     */
    public void enableActions(boolean editable, boolean containsSGF) {
    	setSizeAction.setEnabled(editable);
    	textModeCombo.setEnabled(editable);
    	linkAction.setEnabled(editable);
    	stoneAction.setEnabled(editable);
    	
    	//	Insert a grayed out triangle tool when disabled
    	boolean oldEnabled = toolCombo.isEnabled();
    	if(oldEnabled && !editable) {
    		toolComboModel.insertElementAt(grayTri, 0);
    		toolCombo.setSelectedIndex(0);
    	}
    	else if(editable && !oldEnabled) {
    		toolComboModel.removeElementAt(0);
    	}
    	toolCombo.setEnabled(editable);

    	editSGFAction.setEnabled(containsSGF);
    	clearSGFAction.setEnabled(containsSGF);
    }
	
	/** Displays the preferences dialog. */
	public void onShowPrefs() {
		new SettingsDialog(this);
		if(clickSound == null && GS.getSoundEnabled() || GS.getClickSoundEnabled())
			loadSounds();
	}
    
	/** Saves state and exits. */
	private void onExit() {
		if(!askSaveIfDirty())
			return;
		
		GS.setFrameBounds(getBounds());
		GS.setLastFile(curFile);
		GS.setSplitterPos(splitPane.getDividerLocation());
		GS.setHistoryStack(historyStack);
		GS.setFutureStack(futureStack);
		
		try {
			GS.saveSettings();
		}
		catch(Exception e) {
			Util.log(e);
		}
		
		System.exit(0);
    }

	/** Creates a new file. */
    public void onNewFile() {
		if(!askSaveIfDirty())
			return;
		
    	filename = "Untitled";
    	curFile = null;
    	
    	PageNode root = new PageNode("Untitled");
    	tree.setRoot(root);
    	gotoPage(root, true);
    	
    	historyStack.clear();
    	futureStack.clear();
    	setDirty(false);
	}
    
    /**
     * Opens a file.
     * @param file the file
     */
	public void onOpenFile(File file) {
		if(!askSaveIfDirty())
			return;
		
		PageNode root = loadFile(file);
		tree.setRoot(root);
		
		gotoPage(root, true);
		
		curFile = file;
		filename = file.getName();
		
		historyStack.clear();
		futureStack.clear();
		setDirty(false);
	}
	
	/**
	 * Saves the current file.
	 * @return true if the file was saved successfully
	 */
	public boolean onSaveFile() {
		curPage.leavingPage();
		
		if(curFile == null)
			return onSaveFileAs();
		
		if(saveFile(curFile)) {
			setDirty(false);
			return true;
		}
		
		return false;
	}
	
	/**
	 * Saves the file with a new name.
	 * @return true if the file was saved successfully.
	 */
	public boolean onSaveFileAs() {
		curPage.leavingPage();
		
		String newname = Util.chooseFile(this, gbkFilter, "Save As", true, "Save");
		if(newname == null)
			return false;
		
		File f = new File(newname);
		
		if(!saveFile(f))
			return false;
		
		curFile = f;
		filename = f.getName();
		setDirty(false);
		return true;
	}
	
	/**
	 * If the current file has changed, asks the user if they want to
	 * save their changes.
	 * @return false if the user clicked Cancel in the dialog 
	 */
	private boolean askSaveIfDirty() {
		if(!dirty)
			return true;
		
		int choice = JOptionPane.showConfirmDialog(this, 
				filename + " has changed. Save changes?",
				"Confirm", JOptionPane.YES_NO_CANCEL_OPTION);
		
		if(choice == JOptionPane.CANCEL_OPTION)
			return false;
		if(choice == JOptionPane.NO_OPTION)
			return true;
		
		return onSaveFile();
	}
	
	/** Navigates to the previous page. */
	public void onPrev() {
		PageNode prevPage = (PageNode)curPage.getPreviousNode();
		if(prevPage == null) {
			Toolkit.getDefaultToolkit().beep();
			return;
		}
		
		gotoPage(prevPage, true);
	}
	
	/** Navigates to the next page. */
	public void onNext() {
		PageNode nextPage = (PageNode)curPage.getNextNode();
		if(nextPage == null) {
			Toolkit.getDefaultToolkit().beep();
			return;
		}
		
		gotoPage(nextPage, true);
	}
	
	/** Navigates to the previous page in the history. */
	public void onHistBack() {
		int curPageID = historyStack.removeLast();
		
		PageNode prevPage = null;
		while(prevPage == null) {
			if(historyStack.isEmpty()) {
				historyStack.addLast(curPageID);
				Toolkit.getDefaultToolkit().beep();
				return;
			}
			
			int prevPageID = historyStack.getLast();
			prevPage = tree.getPage(prevPageID);
			if(prevPage == null)
				historyStack.removeLast();
		}
		
		futureStack.addLast(curPageID);
		
		gotoPage(prevPage, false);
	}
	
	/** Navigates to the next page in the history. */
	public void onHistForward() {
		PageNode nextPage = null;
		int nextPageID = -1;
		
		while(nextPage == null) {
			if(futureStack.isEmpty()) {
				Toolkit.getDefaultToolkit().beep();
				return;
			}
			
			nextPageID = futureStack.removeLast();
			nextPage = tree.getPage(nextPageID);
		}
		
		historyStack.addLast(nextPageID);
		gotoPage(nextPage, false);
	}
	
	/**
	 * Loads a file.
	 * @param f the file
	 * @return the new root PageNode
	 */
	private PageNode loadFile(File f) {
		try {
			ZipInputStream zin = new ZipInputStream(new FileInputStream(f));
			zin.getNextEntry();
			ObjectInputStream ois = new ObjectInputStream(zin);
			PageNode root = PageNode.readFromFile(ois);
			ois.close();
			return root;
		}
		catch(Exception e) {
			JOptionPane.showMessageDialog(this, "Error loading file: " + e.getMessage());
			Util.logSilent(e);
			return null;
		}
	}
	
	/**
	 * Saves the current file.
	 * @param f the file
	 * @return true if the file was saved successfully
	 */
	private boolean saveFile(File f) {
		try {
			ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(f));
			ZipEntry entry = new ZipEntry("book.dat");
			zout.putNextEntry(entry);
			
			ObjectOutputStream out = new ObjectOutputStream(zout);
			tree.getRoot().writeToFile(out);
			out.close();
			
			return true;
		}
		catch(IOException e) {
			JOptionPane.showMessageDialog(this, 
					"Error saving file. See viewer.log for details.");
			Util.logSilent(e);
			return false;
		}
	}
	
	/**
	 * Sets the dirty bit, and updates the title bar.
	 * @param d true if there are changes that have not been saved
	 */
	public void setDirty(boolean d) {
		dirty = d;
		
		String title = "GoViewer - " + filename;
		if(dirty)
			title += "*";
		setTitle(title);
	}
	
	//	TODO: Get rid of setStoneTool?
	/** Selects the add stone tool. */
	public void setStoneTool() { editController.setEditTool(TOOL_STONE); }
	
	/**
	 * Loads the click and right/wrong sounds.
	 */
	private static void loadSounds() {
	    Main.clickSound = Applet.newAudioClip(Main.class.getResource("/sounds/click.wav")); //$NON-NLS-1$
	    //	TODO: load right/wrong sounds
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
        System.setProperty("sun.awt.exception.handler", "util.ExceptionHandler"); //$NON-NLS-1$ //$NON-NLS-2$
        
        //	TODO: On SGF with long text, don't scroll to bottom
        //	TODO: Way to resize tree?  splitter with collapse?
        //	TODO: Partial board diagrams
        //	TODO: External links
        //	TODO: Add stones like Drago
        //	TODO: Word wrap in text pane
        //	TODO: Save current dir for filechooser between sessions
        //	TODO: Make Solved/Unsolved prettier
        //	TODO: Ctrl-S to save.  Other accelerators?
        //	TODO: Try sequences on diagram pages
        //	TODO: Undo/redo?
        //	TODO: Update checking, test proxy
        //	TODO: Control area of board shown?
        
        boolean editMode = false;
        for(String arg : args) {
        	if(arg.equalsIgnoreCase("edit"))
        		editMode = true;
        }
        
        new Main(editMode);
	}
}
